module iTimer.Main;

import iTimer.Types; 

auto file   =  `D:\usr\sys\chat\qqmusic_download\下一个天亮.mp3` ;


extern(Windows) {
    pragma(lib, "winmm.lib");
    const WAVE_FORMAT_PCM=1;
    const UINT WAVE_MAPPER= -1;  // FIXME: This doesn't make sense!
    const CALLBACK_TYPEMASK = 0x70000;
    const CALLBACK_NULL     = 0;
    const CALLBACK_WINDOW   = 0x10000;
    const CALLBACK_TASK     = 0x20000;
    const CALLBACK_FUNCTION = 0x30000;
    const CALLBACK_THREAD   = CALLBACK_TASK;
    const CALLBACK_EVENT    = 0x50000;

    typedef HANDLE HWAVE;
    typedef HANDLE HWAVEIN;
    typedef HANDLE HWAVEOUT;
    typedef HANDLE HDRVR;
    alias HWAVEIN* LPHWAVEIN;
    alias HWAVEOUT* LPHWAVEOUT;
    alias UINT MMRESULT;
    alias uint*     LPDWORD, LPUINT;    // alias uint*       PULONG, PDWORD, LPDWORD, PUINT, LPUINT;

    const TIME_MS      = 1;
    const TIME_SAMPLES = 2;
    const TIME_BYTES   = 4;
    const TIME_SMPTE   = 8;
    const TIME_MIDI    = 16;
    const TIME_TICKS   = 32;

    const MMSYSERR_BASE     =    0;
    const WAVERR_BASE       =   32;
    const MIDIERR_BASE      =   64;
    const TIMERR_BASE       =   96;
    const JOYERR_BASE       =  160;
    const MCIERR_BASE       =  256;
    const MIXERR_BASE       = 1024;
    const MCI_STRING_OFFSET =  512;
    const MCI_VD_OFFSET     = 1024;
    const MCI_CD_OFFSET     = 1088;
    const MCI_WAVE_OFFSET   = 1152;
    const MCI_SEQ_OFFSET    = 1216;
    
    enum {
        MMSYSERR_NOERROR        = 0,
        MMSYSERR_ERROR          = MMSYSERR_BASE+1,
        MMSYSERR_BADDEVICEID, // 2
        MMSYSERR_NOTENABLED, // 3
        MMSYSERR_ALLOCATED, // 4
        MMSYSERR_INVALHANDLE, // 5
        MMSYSERR_NODRIVER, // 6
        MMSYSERR_NOMEM, //7
        MMSYSERR_NOTSUPPORTED, //8 
        MMSYSERR_BADERRNUM, //9 
        MMSYSERR_INVALFLAG, // 10
        MMSYSERR_INVALPARAM, //11
        MMSYSERR_HANDLEBUSY, //12
        MMSYSERR_INVALIDALIAS, //13
        MMSYSERR_BADDB, // 14
        MMSYSERR_KEYNOTFOUND, // 15
        MMSYSERR_READERROR, // 16
        MMSYSERR_WRITEERROR, //17
        MMSYSERR_DELETEERROR, //18
        MMSYSERR_VALNOTFOUND, //19
        MMSYSERR_NODRIVERCB, // = MMSYSERR_BASE+20
        MMSYSERR_LASTERROR      = MMSYSERR_NODRIVERCB // 20
    }
    enum {
        MM_JOY1MOVE            = 0x3A0,
        MM_JOY2MOVE,
        MM_JOY1ZMOVE,
        MM_JOY2ZMOVE,       // = 0x3A3
        MM_JOY1BUTTONDOWN      = 0x3B5,
        MM_JOY2BUTTONDOWN,
        MM_JOY1BUTTONUP,
        MM_JOY2BUTTONUP,
        MM_MCINOTIFY,       // = 0x3B9
        MM_WOM_OPEN            = 0x3BB,
        MM_WOM_CLOSE,
        MM_WOM_DONE,
        MM_WIM_OPEN,
        MM_WIM_CLOSE,
        MM_WIM_DATA,
        MM_MIM_OPEN,
        MM_MIM_CLOSE,
        MM_MIM_DATA,
        MM_MIM_LONGDATA,
        MM_MIM_ERROR,
        MM_MIM_LONGERROR,
        MM_MOM_OPEN,
        MM_MOM_CLOSE,
        MM_MOM_DONE,        // = 0x3C9
        MM_DRVM_OPEN           = 0x3D0,
        MM_DRVM_CLOSE,
        MM_DRVM_DATA,
        MM_DRVM_ERROR,
        MM_STREAM_OPEN,
        MM_STREAM_CLOSE,
        MM_STREAM_DONE,
        MM_STREAM_ERROR,    // = 0x3D7
        MM_MOM_POSITIONCB      = 0x3CA,
        MM_MCISIGNAL,
        MM_MIM_MOREDATA,    // = 0x3CC
        MM_MIXM_LINE_CHANGE    = 0x3D0,
        MM_MIXM_CONTROL_CHANGE = 0x3D1
    }
    const WOM_OPEN  = MM_WOM_OPEN;
    const WOM_CLOSE = MM_WOM_CLOSE;
    const WOM_DONE  = MM_WOM_DONE;
    const WIM_OPEN  = MM_WIM_OPEN;
    const WIM_CLOSE = MM_WIM_CLOSE;
    const WIM_DATA  = MM_WIM_DATA;

    align(1):
    struct WAVEHDR {
        LPSTR lpData;
        DWORD dwBufferLength;
        DWORD dwBytesRecorded;
        DWORD dwUser;
        DWORD dwFlags;
        DWORD dwLoops;
        WAVEHDR *lpNext;
        DWORD reserved;
    }
    alias WAVEHDR* PWAVEHDR, LPWAVEHDR;

    struct WAVEFORMATEX {
        WORD wFormatTag;
        WORD nChannels;
        DWORD nSamplesPerSec;
        DWORD nAvgBytesPerSec;
        WORD nBlockAlign;
        WORD wBitsPerSample;
        WORD cbSize;
    }
    alias WAVEFORMATEX* PWAVEFORMATEX, LPWAVEFORMATEX;
    alias WAVEFORMATEX* LPCWAVEFORMATEX;
    
    struct MMTIME {
        UINT wType;
        union {
            DWORD ms;
            DWORD sample;
            DWORD cb;
            DWORD ticks;
            struct smpte {
                BYTE hour;
                BYTE min;
                BYTE sec;
                BYTE frame;
                BYTE fps;
                BYTE dummy;
                BYTE pad[2];
            };
            struct midi {
                DWORD songptrpos;
            }
        }
    }
    alias MMTIME* PMMTIME, LPMMTIME;
    struct TIMECAPS {
        UINT wPeriodMin;
        UINT wPeriodMax;
    }
    alias TIMECAPS* PTIMECAPS, LPTIMECAPS;
    alias void function (UINT,UINT,DWORD,DWORD,DWORD) LPTIMECALLBACK;

    LRESULT CloseDriver(HDRVR,LONG,LONG);
    HDRVR OpenDriver(LPCWSTR,LPCWSTR,LONG);
    LRESULT SendDriverMessage(HDRVR,UINT,LONG,LONG);
    HMODULE DrvGetModuleHandle(HDRVR);
    HMODULE GetDriverModuleHandle(HDRVR);
    LRESULT DefDriverProc(DWORD,HDRVR,UINT,LPARAM,LPARAM);
    UINT mmsystemGetVersion();
    // FIXME: I believe this next line is a mistake
    //alias OutputDebugString OutputDebugStr;
    BOOL sndPlaySoundA(LPCSTR,UINT);
    BOOL sndPlaySoundW(LPCWSTR,UINT);
    BOOL PlaySoundA(LPCSTR,HMODULE,DWORD);
    BOOL PlaySoundW(LPCWSTR,HMODULE,DWORD);
    UINT waveOutGetNumDevs();
    MMRESULT waveOutGetVolume(HWAVEOUT,PDWORD);
    MMRESULT waveOutSetVolume(HWAVEOUT,DWORD);
    MMRESULT waveOutGetErrorTextA(MMRESULT,LPSTR,UINT);
    MMRESULT waveOutGetErrorTextW(MMRESULT,LPWSTR,UINT);
    MMRESULT waveOutOpen(LPHWAVEOUT,UINT,LPCWAVEFORMATEX,DWORD,DWORD,DWORD);
    MMRESULT waveOutClose(HWAVEOUT);
    MMRESULT waveOutPrepareHeader(HWAVEOUT,LPWAVEHDR,UINT);
    MMRESULT waveOutUnprepareHeader(HWAVEOUT,LPWAVEHDR,UINT);
    MMRESULT waveOutWrite(HWAVEOUT,LPWAVEHDR,UINT);
    MMRESULT waveOutPause(HWAVEOUT);
    MMRESULT waveOutRestart(HWAVEOUT);
    MMRESULT waveOutReset(HWAVEOUT);
    MMRESULT waveOutBreakLoop(HWAVEOUT);
    MMRESULT waveOutGetPosition(HWAVEOUT,LPMMTIME,UINT);
    MMRESULT waveOutGetPitch(HWAVEOUT,PDWORD);
    MMRESULT waveOutSetPitch(HWAVEOUT,DWORD);
    MMRESULT waveOutGetPlaybackRate(HWAVEOUT,PDWORD);
    MMRESULT waveOutSetPlaybackRate(HWAVEOUT,DWORD);
    MMRESULT waveOutGetID(HWAVEOUT,LPUINT);
    MMRESULT waveOutMessage(HWAVEOUT,UINT,DWORD,DWORD);
    MMRESULT timeGetSystemTime(LPMMTIME,UINT);
    DWORD timeGetTime();
    MMRESULT timeSetEvent(UINT,UINT,LPTIMECALLBACK,DWORD,UINT);
    MMRESULT timeKillEvent(UINT);
    MMRESULT timeGetDevCaps(LPTIMECAPS,UINT);
    MMRESULT timeBeginPeriod(UINT);
    MMRESULT timeEndPeriod(UINT);
}

struct iPlayer {
    alias typeof(this) This;
    alias This* pThis;
    private {
        static bool is_mpg123_inited ;
        static char[] _error_message ;
    }
    enum Status {
        Closed ,
        Stoped ,
        Stoping  ,
        Playing ,
        Paused ,
    }
    private {
        mpg123_handle*   decoder ;
        size_t      buffer_size , block_size , total,  played , _length ;
        c_int       channels;
        c_int       encoding_size ;
        c_long     rate ;
        char[]      file ;
        char[]      err_msg ;
        HWAVEOUT        _ghWaveOut = NULL ;
        WAVEHDR[2]      WaveHdr ;
        ubyte[][2]   _bu ;
        Mutex   mux ;

        c_double   current_seconds ;
        c_double   seconds_left;
        ptrdiff_t   current_frame;
        ptrdiff_t   frames_left;
        ulong      frame_counter ;
        Status     stat ;
    }
    
    static bool Init() {
        if( !is_mpg123_inited ) {
            auto ret = mpg123_init();
            if(ret !is MPG123_OK) {
                _error_message = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                return  false;
            }
            is_mpg123_inited    = true ;
        }
        return true ;
    }
    
    static bool UnInit(){
        if( is_mpg123_inited ) {
            auto ret = mpg123_init();
            if(ret !is MPG123_OK) {
                _error_message  = fromStringz(mpg123_plain_strerror(ret)) ;
                return  false;
            }
            is_mpg123_inited    = false ;
        }
        return true ;
    }
    
    static void show_err(char[] fn, MMRESULT err){
        wchar[1024] tmp = 0 ;
        if( err !is 0 ) {
            MMRESULT ret    = waveOutGetErrorTextW(err, tmp.ptr,  tmp.length) ;
            log("{} = {}:{}  `{}`", fn , err, ret, fromString16z(tmp.ptr));
        }
    }
    
    bool Open(char[] file) {
        if( !Init ) {
            return false ;
        }
        if( mux is null ) {
            mux = new Mutex ;
        }
        Clear();
        if( !Pth.exists(file) ){
            err_msg = "File not exists.";
            return false ;
        }
        this.file   = file ;
        int err;
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        decoder = mpg123_new(null , &err) ;
        if( decoder is null || err !is MPG123_OK) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(err)) ;
            return false ;
        }
        auto  ret = mpg123_open(decoder, cast(char*)toStringz(file) );
        if(ret !is MPG123_OK) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        
        c_int       encoding ;
        ret = mpg123_getformat(decoder, 
			&rate, 
			&channels,
			&encoding
		);
        if(ret !is MPG123_OK) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        assert(encoding is MPG123_ENC_SIGNED_16);
        
        block_size    = mpg123_outblock(decoder);
        if( block_size <= 0 || block_size >=  ushort.max  ) {
            err_msg = "mpg123_outblock error" ;
            return false;
        }
        encoding_size   = mpg123_encsize(encoding);
        _length = mpg123_length(decoder)  ;
        
        // log("rate={}, channels={}, encoding_size={}  length={} block_size={}", rate,  channels, encoding_size, _length, block_size);
        
        ret = mpg123_format_none(decoder);
        if( ret !is MPG123_OK ) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        
        ret = mpg123_format(decoder, rate, channels, encoding);
        if( ret !is MPG123_OK ) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        
        buffer_size = block_size * 2 ;
        _bu[0]  = new ubyte[ buffer_size ] ;
        _bu[1]  = new ubyte[ buffer_size ] ;
        assert(channels is 2);

        
        assert(waveOutGetNumDevs());
        
        WAVEFORMATEX outFormatex ;
        outFormatex.wFormatTag      = WAVE_FORMAT_PCM;
        outFormatex.wBitsPerSample  =  encoding_size * 8 ;
        outFormatex.nChannels           = channels ;
        outFormatex.nSamplesPerSec  =  rate;
        outFormatex.nAvgBytesPerSec = outFormatex.nSamplesPerSec * outFormatex.nChannels * outFormatex.wBitsPerSample / 8 ;
        outFormatex.nBlockAlign         = outFormatex.nChannels * outFormatex.wBitsPerSample / 8 ;
        
        MMRESULT    mmr ;
        mmr = waveOutOpen(&_ghWaveOut, WAVE_MAPPER, &outFormatex, cast(DWORD) &SpeakerCallback, cast(DWORD) this , CALLBACK_FUNCTION);
        show_err("waveOutOpen", mmr);
        
        return true ;
    }
    
    private static extern(Windows) void SpeakerCallback (HWAVEOUT _waveoutdev, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2){
        iPlayer* _this = cast(iPlayer*) dwInstance ;
        _this.mux.lock();
        scope(exit) {
            _this.mux.unlock();
        }
        switch(uMsg) {
            case WOM_DONE:
                ptrdiff_t   frame_offset    = 0 ;
                ptrdiff_t   buffered_bytes = 0 ;
                c_int ret    = mpg123_position( _this.decoder, frame_offset, buffered_bytes, &_this.current_frame, &_this.frames_left, &_this.current_seconds, &_this.seconds_left);
            
                PWAVEHDR  _ptr  = cast(PWAVEHDR) dwParam1 ;
                c_uint done ;
                ret = mpg123_read(_this.decoder,  cast(ubyte*) _ptr.lpData  , _this.buffer_size , &done) ;
                if( ret !is MPG123_OK || done >  _this.buffer_size ) {
                    _this.err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                    return  ;
                }
                _ptr.dwBufferLength  = done ;
                ret =   waveOutWrite ( _this._ghWaveOut, _ptr, WAVEHDR.sizeof ) ;
                _this.frame_counter++;
                
                break ;
            case WOM_CLOSE:
            default:
                _this.stat  = Status.Stoping ; 
                break ;
        }
    }
        
    bool Play(){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( stat !is Status.Playing ) {
            
            // Header initialisieren und vorbereiten
            WaveHdr[0].lpData          = cast(char*) _bu[0].ptr ;
            WaveHdr[0].dwBufferLength  = buffer_size ;
            WaveHdr[0].dwBytesRecorded = 0 ;
            WaveHdr[0].dwUser          = 0 ;
            WaveHdr[0].dwFlags         = 0 ;
            WaveHdr[0].dwLoops         = 0 ;
            WaveHdr[0].lpNext           = null ;
            WaveHdr[0].reserved        = 0 ;

            MMRESULT ret=waveOutPrepareHeader (_ghWaveOut, & WaveHdr[0] , WAVEHDR.sizeof ) ;
            show_err ("waveOutPrepareHeader", ret);
            
            // Header initialisieren und vorbereiten
            WaveHdr[1].lpData          = cast(char*) _bu[1].ptr ;
            WaveHdr[1].dwBufferLength  = buffer_size ;
            WaveHdr[1].dwBytesRecorded = 0 ;
            WaveHdr[1].dwUser          = 0 ;
            WaveHdr[1].dwFlags         = 0 ;
            WaveHdr[1].dwLoops         = 0 ;
            WaveHdr[1].lpNext           = null ;
            WaveHdr[1].reserved        = 0 ;
            
            ret=waveOutPrepareHeader (_ghWaveOut, & WaveHdr[1] , WAVEHDR.sizeof ) ;
            show_err ("waveOutPrepareHeader", ret);
            
            c_uint done ;
            ret = mpg123_read(decoder, _bu[0].ptr  , buffer_size , &done);
            if( ret !is MPG123_OK || done > buffer_size ) {
                err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                return false ;
            }
            WaveHdr[1].dwBufferLength  = done ;
            ret =   waveOutWrite (_ghWaveOut, & WaveHdr[0], WAVEHDR.sizeof ) ;
            
            ret = mpg123_read(decoder, _bu[1].ptr  , buffer_size , &done);
            if( ret !is MPG123_OK || done > buffer_size ) {
                err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                return false ;
            }
            WaveHdr[1].dwBufferLength  = done ;
            ret =   waveOutWrite (_ghWaveOut, & WaveHdr[1], WAVEHDR.sizeof ) ;
            
            stat    = Status.Playing ;
        }
        return true ;
    }
    
    void Run(){
        while(true) {
  
            Thread.sleep(1);
            
            log("current_seconds={},  seconds_left={} stat={} frame_counter={} current_frame={} frames_left={}",  current_seconds , seconds_left ,  this.stat, frame_counter , current_frame, frames_left );
 
            mux.lock();
            scope(exit) {
                mux.unlock();
            }
            if( frames_left <= 1 ) {
                break;
            }
        }
    }
    
    void Close() {
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( _ghWaveOut !is null ) {
            waveOutClose(_ghWaveOut) ;
            _ghWaveOut  = null ;
        }
        if( decoder !is null ) {
            mpg123_close(decoder);
            mpg123_delete(decoder);
            decoder = null ;
        }
        current_seconds = 0 ;
        seconds_left = 0 ;
        frame_counter   = 0 ;
        stat    = Status.Closed ;
    }
    
    bool Pause(){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( _ghWaveOut !is null ) {
            waveOutPause(_ghWaveOut);
        }
        return true ;
    }
    
    void Clear(){
        Close;
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        err_msg = null ;
        file    = null ;
    }
    
    char[] ErrorMessage(){
        if( _error_message !is null ) {
            return _error_message ;
        }
        return err_msg ;
    }
    
}

void main(char[][] args)
{
    // GC.disable;
    
    iPlayer ip ;
    if( !ip.Open( cast(char[]) file) ){
        log("{}", ip.ErrorMessage);
    }

    if( !ip.Play() ){
        log("{}", ip.ErrorMessage);
    }
    
    ip.Run();
    
}



